{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Calibration With Three Receivers"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Intro"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "It has long been known that full error correction is possible given a VNA  with only three recievers and no internal coaxial switch.  However, since no modern  VNA employs  such  an  architecture, the software required to make fully corrected measurements is not available on today's modern VNA's. \n",
    "\n",
    "Recently, the application of Frequency Extender units containing only three receivers has become more common.  Thus, there is a need for full error correction capability on systems with three receivers and no  internal coaxial switch. This document describes how to use [scikit-rf](http://www.scikit-rf.org) to fully correct two-port measurements made on such a system."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Theory"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "A circuit model for a switch-less three receiver system is shown below."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "from IPython.display import *\n",
    "SVG('three_receiver_cal/pics/vnaBlockDiagramForwardRotated.svg')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To fully correct an arbitrary two-port,  the device must be measured in two orientations, call these forward and reverse. Because there is no switch present, this requires the operator to physically flip the device, and save the pair of measurements. In on-wafer scenarios, one could fabricate two identical devices, one in each orientation. In either case, a pair of  measurements are required for each DUT before correction can occur. \n",
    "\n",
    "While in reality the device is being flipped, one can imaging that the device is static, and the entire VNA circuitry is flipped. This interpretation lends itself to implementation, as the existing 12-term correction can be re-used by simply copying the forward error coefficients into the corresponding reverse error coefficients. This is what `scikit-rf` does internally.\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Worked Example"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This example demonstrates how to create a [TwoPortOnePath](http://scikit-rf.readthedocs.org/en/latest/reference/calibration/generated/skrf.calibration.calibration.TwoPortOnePath.html#skrf.calibration.calibration.TwoPortOnePath)  and [EnhancedResponse](http://scikit-rf.readthedocs.org/en/latest/reference/calibration/generated/skrf.calibration.calibration.EnhancedResponse.html#skrf.calibration.calibration.EnhancedResponse) calibration from  measurements taken on a Agilent PNAX with a set of VDI WR-12 TXRX-RX Frequency Extender heads.  Comparisons between the two algorithms are made by correcting an asymmetric  DUT."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Read in the Data"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The measurements of the calibration standards and DUT's  were downloaded from the VNA by saving touchstone files of the raw s-parameter data to disk. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "ls three_receiver_cal/data/"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "These files can be read by scikit-rf into `Network`s with the following. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "import skrf as rf \n",
    "%matplotlib inline\n",
    "from pylab import * \n",
    "rf.stylely()\n",
    "\n",
    "\n",
    "raw = rf.read_all_networks('three_receiver_cal/data/')\n",
    "# list the raw measurments \n",
    "raw.keys()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Each `Network` can be accessed through the dictionary `raw`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "thru = raw['thru']\n",
    "thru"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If we look at the *raw* measurement of the flush thru, it can be seen that only $S_{11}$ and $S_{21}$ contain meaningful data.  The other s-parameters are noise. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "thru.plot_s_db()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Create Calibration "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In the code that follows a TwoPortOnePath calibration is created from corresponding  measured and ideal responses of the calibration standards. The measured networks are read from disk, while their corresponding ideal responses are generated using scikit-rf. More information about using scikit-rf to do offline calibrations can be found [here](http://scikit-rf.readthedocs.org/en/latest/tutorials/calibration.html). "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "from skrf.calibration import TwoPortOnePath\n",
    "from skrf.media import RectangularWaveguide\n",
    "from skrf import two_port_reflect as tpr\n",
    "from skrf import  mil\n",
    "\n",
    "# pull frequency information from measurements\n",
    "frequency = raw['short'].frequency\n",
    "\n",
    "# the media object \n",
    "wg = RectangularWaveguide(frequency=frequency, a=120*mil, z0=50)\n",
    "\n",
    "# list of 'ideal' responses of the calibration standards\n",
    "ideals = [wg.short(nports=2),\n",
    "          tpr(wg.delay_short( 90,'deg'), wg.match()),\n",
    "          wg.match(nports=2),\n",
    "          wg.thru()]\n",
    "\n",
    "# corresponding measurments to the 'ideals'\n",
    "measured = [raw['short'],\n",
    "            raw['quarter wave delay short'],\n",
    "            raw['load'],\n",
    "            raw['thru']]\n",
    "\n",
    "# the Calibration object\n",
    "cal = TwoPortOnePath(measured = measured, ideals = ideals )"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Apply Correction"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "There are two types of correction possible with a 3-receiver system. \n",
    "\n",
    "1. Full (TwoPortOnePath)\n",
    "2. Partial  (EnhancedResponse)\n",
    "\n",
    "`scikit-rf` uses the same `Calibration` object for both, but employs different correction  algorithms depending on  the `type` of the DUT. The DUT used in  this example is a WR-15 shim  cascaded with a WR-12 1\" straight waveguide, as shown in the picture below.  Measurements of this DUT are corrected with both *full* and *partial* correction and the results are compared below. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "Image('three_receiver_cal/pics/asymmetic DUT.jpg', width='75%')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Full Correction ( TwoPortOnePath)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Full correction is achieved by measuring each device in both orientations, **forward** and **reverse**. To be clear, this means that  the DUT must be  physically removed, flipped, and re-inserted. The resulting pair of  measurements are then passed to the `apply_cal()` function as a tuple. This returns a single corrected response.  \n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "#### Partial Correction (Enhanced Response)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If you pass a single measurement to the `apply_cal()` function, then the calibration will employ partial correction. This type of correction is known as `EnhancedResponse`. Depending on the measurment application, this type of correction may be  *good enough*, and perhaps the only choice."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Comparison"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Below are direct comparisons of the DUT shown above corrected with  *full*  and *partial* algorithms.  It shows that the partial calibration produces a large ripple on the reflect measurements, and slightly larger ripple on the transmissive measurments. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "collapsed": false
   },
   "outputs": [],
   "source": [
    "from pylab import * \n",
    "\n",
    "simulation = raw['simulation']\n",
    "\n",
    "dutf = raw['wr15 shim and swg (forward)']\n",
    "dutr = raw['wr15 shim and swg (reverse)']\n",
    "\n",
    "corrected_full =     cal.apply_cal((dutf, dutr)) \n",
    "corrected_partial =  cal.apply_cal(dutf) \n",
    "\n",
    "\n",
    "\n",
    "# plot results\n",
    "\n",
    "f, ax = subplots(1,2, figsize=(8,4))\n",
    "\n",
    "ax[0].set_title ('$S_{11}$')\n",
    "ax[1].set_title ('$S_{21}$')\n",
    "\n",
    "corrected_partial.plot_s_db(0,0, label='Partial Correction',ax=ax[0])\n",
    "corrected_partial.plot_s_db(1,0, label='Partial Correction',ax=ax[1])\n",
    "\n",
    "corrected_full.plot_s_db(0,0, label='Full Correction', ax = ax[0])\n",
    "corrected_full.plot_s_db(1,0, label='Full Correction', ax = ax[1])\n",
    "\n",
    "simulation.plot_s_db(0,0,label='Simulation', ax=ax[0], color='k')\n",
    "simulation.plot_s_db(1,0,label='Simulation', ax=ax[1], color='k')\n",
    "\n",
    "tight_layout()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### What if my DUT is Symmetric??"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If the DUT is known to be **reciprocal** ( $S_{21}=S_{12}$ ) and **symmetric** ( $S_{11}=S_{22}$ ), then its response should be the identical for both forward anad reverse orientations. In this case, measuring the device twice is unnecessary, and can be circumvented. This is explored in the example: [TwoPortOnePath, EnhancedResponse, and FakeFlip](TwoPortOnePath, EnhancedResponse, and FakeFlip.ipynb)"
   ]
  }
 ],
 "metadata": {
  "anaconda-cloud": {},
  "kernelspec": {
   "display_name": "Python [default]",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.5.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 0
}
